package pl.streamsoft.test.currency_buisness;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import pl.streamsoft.test.currency_buisness.dto.CurrencyDTO;
import pl.streamsoft.test.currency_buisness.dto.CurrencyTypeDTO;
import pl.streamsoft.test.currency_buisness.exceptions.ServiceException;
import pl.streamsoft.test.currency_buisness.nbp.TabelaKursow;
import pl.streamsoft.test.currency_buisness.nbp.TabelaKursow.Pozycja;
import pl.streamsoft.test.model.CurrenciesDAO;
import pl.streamsoft.test.model.CurrenciesHibernate;
import pl.streamsoft.test.model.entities.CurrencyAverageEntity;
import pl.streamsoft.test.model.entities.CurrencyEntity;
import pl.streamsoft.test.model.entities.CurrencyTypeEntity;
import pl.streamsoft.test.model.entities.DateEntryEntity;
import pl.streamsoft.test.model.exceptions.DAOException;

@Component
@ComponentScan(basePackages = { "pl.streamsoft.test.model" })
@Scope(value = "singleton")
public class CurrenciesService {

	@Autowired(required = true)
	@Qualifier("CurrenciesHibernateBean")
	private CurrenciesDAO dao;

	@Autowired(required = true)
	@Qualifier("CurrenciesHibernateBean")
	private CurrenciesDAO readonlyDao;

	public CurrenciesService() {

	}

	public <T> T makeTransaction(TransactionCommand<T> command,
			CurrenciesDAO dao) throws ServiceException {

		try {
			dao.beginTransaction();
			T resultObject = command.execute(dao);
			dao.commitTransaction();
			return (T) resultObject;
		} catch (Throwable exception) {
			try {
				dao.rollbackTransaction();
			} catch (DAOException e) {
				throw new ServiceException("Transaction rollback failed. "
						+ exception.getLocalizedMessage());
			}
			throw new ServiceException("Transaction rollback. "
					+ exception.getLocalizedMessage());
		}
	}

	@Transactional
	public Void downloadCurrencies(final Date from, final Date to,
			final String currencyAcronym) throws ServiceException {

		return makeTransaction(new TransactionCommand<Void>() {
			public Void execute(CurrenciesDAO dao) throws Exception {

				Calendar start = Calendar.getInstance();
				start.setTime(from);

				Calendar end = Calendar.getInstance();
				end.setTime(to);

				NBPApi nbpApi = new NBPApi();
				nbpApi.initialize();

				CurrencyTypeEntity currencyType = findOrCreateCurrencyTypeEntity(
						currencyAcronym, dao);
				while (!start.after(end)) {
					Date targetDay = start.getTime();
					DateEntryEntity dateEntryEntiry = findOrCreateDateEntryEntity(targetDay);

					TabelaKursow table = nbpApi.getCurrencyTable(targetDay);
					if (table != null) {
						List<Pozycja> list = table.getPozycja();
						for (Pozycja pozycja : list) {
							if (currencyAcronym.trim().equalsIgnoreCase(
									pozycja.getKodWaluty().trim())) {
								CurrencyEntity entity = new CurrencyEntity();
								entity.setDate(dateEntryEntiry);
								entity.setType(currencyType);
								entity.setValue(pozycja.getKursSredni());

								dao.insert(entity);
							}
						}
					}
					start.add(Calendar.DAY_OF_MONTH, 1);
				}

				updateCurrencyAverage(currencyType);
				return null;

			}
		}, dao);
	}

	@Transactional
	public List<CurrencyDTO> getCurrencies(final Date from, final Date to,
			final String currencyAcronym) throws ServiceException {

		return makeTransaction(new TransactionCommand<List<CurrencyDTO>>() {
			public List<CurrencyDTO> execute(CurrenciesDAO dao)
					throws Exception {
				List<CurrencyDTO> resultList = new ArrayList<CurrencyDTO>();

				List<CurrencyEntity> list;

				if (currencyAcronym.isEmpty()) {
					list = dao.findCurrenciesByDate(from, to);
				} else {
					CurrencyTypeEntity currencyType = dao.findCurrencyType(currencyAcronym);
					list = dao.findCurrenciesByDateAndType(from, to,
							currencyType);
				}

				for (CurrencyEntity entity : list) {
					resultList.add(toDTO(entity));
				}
				return resultList;
			}
		}, readonlyDao);

	}

	@Transactional
	public List<CurrencyTypeDTO> getCurrenciesTypes() throws ServiceException {

		return makeTransaction(new TransactionCommand<List<CurrencyTypeDTO>>() {
			public List<CurrencyTypeDTO> execute(CurrenciesDAO dao)
					throws Exception {
				List<CurrencyTypeDTO> list = new ArrayList<CurrencyTypeDTO>();

				List<CurrencyTypeEntity> entities = dao.findAllCurrencyTypes();
				for (CurrencyTypeEntity entity : entities) {
					list.add(toDTO(entity));
				}

				return list;
			}
		}, readonlyDao);
	}

	@Transactional
	public Void delete(final CurrencyDTO dto) throws ServiceException {

		return makeTransaction(new TransactionCommand<Void>() {
			public Void execute(CurrenciesDAO dao) throws Exception {

				CurrencyEntity entity = new CurrencyEntity();
				entity.setId(dto.id);

				dao.delete(entity);

				CurrencyTypeEntity currencyType = findOrCreateCurrencyTypeEntity(
						dto.acronym, dao);
				updateCurrencyAverage(currencyType);
				return null;
			}
		}, dao);
	}

	@Transactional
	public Void update(final CurrencyDTO dto) throws ServiceException {
		return makeTransaction(new TransactionCommand<Void>() {
			public Void execute(CurrenciesDAO dao) throws Exception {
				CurrencyEntity entity;
				entity = fromDTO(dto);
				dao.update(entity);

				CurrencyTypeEntity currencyType = findOrCreateCurrencyTypeEntity(
						dto.acronym, dao);
				updateCurrencyAverage(currencyType);
				return null;
			}
		}, dao);

	}

	@Transactional
	protected DateEntryEntity findOrCreateDateEntryEntity(Date date)
			throws DAOException {
		DateEntryEntity dateEntryEntity;
		try {
			dateEntryEntity = this.dao.findDateEntry(date);
		} catch (DAOException e) {
			dateEntryEntity = new DateEntryEntity(date);
			dateEntryEntity = this.dao.insert(dateEntryEntity);
		}
		return dateEntryEntity;
	}

	protected CurrencyTypeEntity findOrCreateCurrencyTypeEntity(String acronym,
			CurrenciesDAO dao) throws DAOException {
		CurrencyTypeEntity currencyTypeEntity;
		try {
			currencyTypeEntity = dao.findCurrencyType(acronym);
		} catch (DAOException e) {
			currencyTypeEntity = new CurrencyTypeEntity();
			currencyTypeEntity.setAcronym(acronym);
			currencyTypeEntity = dao.insert(currencyTypeEntity);
		}
		return currencyTypeEntity;
	}

	protected CurrencyAverageEntity findOrCreateCurrencyAverageEntity(
			CurrencyTypeEntity currencyType, CurrenciesDAO dao)
			throws DAOException {
		CurrencyAverageEntity currencyAverageEntity;
		try {
			currencyAverageEntity = dao.findCurrencyAverage(currencyType);
		} catch (DAOException e) {
			currencyAverageEntity = new CurrencyAverageEntity();
			currencyAverageEntity.setValue(0);
			currencyAverageEntity.setCurrencyType(currencyType);
			currencyAverageEntity = dao.insert(currencyAverageEntity);
		}
		return currencyAverageEntity;
	}

	@Transactional
	public Void create(final CurrencyDTO dto) throws ServiceException {
		return makeTransaction(new TransactionCommand<Void>() {
			public Void execute(CurrenciesDAO dao) throws Exception {

				CurrencyEntity entity = fromDTO(dto);
				dao.insert(entity);
				CurrencyTypeEntity currencyType = findOrCreateCurrencyTypeEntity(
						dto.acronym, dao);
				updateCurrencyAverage(currencyType);

				return null;
			}
		}, dao);

	}

	protected void updateCurrencyAverage(CurrencyTypeEntity currencyType)
			throws DAOException {

		int itemsByPass = 10;
		double sum = 0;
		long count = 0;
		int pass = 0;
		List<CurrencyEntity> list = new ArrayList<CurrencyEntity>();
		do {
			list = dao.findAllCurrencies(currencyType, itemsByPass, itemsByPass
					* pass);
			if (list.isEmpty()) {
				break;
			}

			for (CurrencyEntity item : list) {
				sum += item.getValue();
				count++;
			}
			pass++;
		} while (list.size() > 0);

		double average = 0;
		if (count != 0) {
			average = sum / count;
		}
		CurrencyAverageEntity currencyAverage = findOrCreateCurrencyAverageEntity(
				currencyType, dao);
		currencyAverage.setValue(average);
		dao.update(currencyAverage);

	}

	@Transactional
	public Double getAverage(final CurrencyTypeDTO dto) throws ServiceException {
		return makeTransaction(new TransactionCommand<Double>() {
			public Double execute(CurrenciesDAO dao) throws Exception {

				CurrencyTypeEntity currencyType = findOrCreateCurrencyTypeEntity(
						dto.acronym, dao);
				CurrencyAverageEntity currencyAverage = findOrCreateCurrencyAverageEntity(
						currencyType, dao);
				return currencyAverage.getValue();
			}
		}, readonlyDao);
	}

	@Transactional
	private CurrencyEntity fromDTO(CurrencyDTO dto) throws DAOException {
		DateEntryEntity dateEntryEntity = findOrCreateDateEntryEntity(dto.date);
		CurrencyTypeEntity currencyTypeEntity = findOrCreateCurrencyTypeEntity(
				dto.acronym, this.dao);

		CurrencyEntity currencyEntity = new CurrencyEntity();
		currencyEntity.setId(dto.id);
		currencyEntity.setValue(dto.value);
		currencyEntity.setDate(dateEntryEntity);
		currencyEntity.setType(currencyTypeEntity);

		return currencyEntity;
	}

	private CurrencyDTO toDTO(CurrencyEntity entity) {
		CurrencyDTO dto = new CurrencyDTO();
		dto.id = entity.getId();
		dto.name = entity.getType().getName();
		dto.acronym = entity.getType().getAcronym();
		dto.date = entity.getDate().getValue();
		dto.value = entity.getValue();

		return dto;
	}

	@SuppressWarnings("unused")
	private CurrencyTypeEntity fromDTO(CurrencyTypeDTO dto) {
		CurrencyTypeEntity entity = new CurrencyTypeEntity();
		entity.setAcronym(dto.acronym);
		entity.setId(dto.id);
		entity.setName(dto.name);
		return entity;
	}

	private CurrencyTypeDTO toDTO(CurrencyTypeEntity entity) {
		CurrencyTypeDTO dto = new CurrencyTypeDTO();

		dto.id = entity.getId();
		dto.name = entity.getName();
		dto.acronym = entity.getAcronym();
		return dto;
	}

}
